#include "../../interfaces/memalloc.h"
#include "gtest/gtest.h"
#include <errno.h>
#include <pthread.h>
#include <sys/resource.h>
#include <unistd.h>

#define Kirikusan_TIMES_INIT 10
#define Kirikusan_TIMES_GET 1024/*4294967296*/
#define Kirikusan_TIMES_RELEASE Kirikusan_TIMES_GET

#define Kirikusan_SWAP_SIZE 660000
#define Kirikusan_MEM_SIZE 330000
#define Kirikusan_SChunkSize 1000

#define Kirikusan_PRI() /*fprintf(stderr,"%s __LINE__ %3d\n",__FUNCTION__,__LINE__)*/
#define Kirikusan_FAIL_ME() {\
			int * y = NULL;\
			*y = 18;\
		}

rlimit Kirikusan_maxfd;
rlimit Kirikusan_maxfd_old;

int Kirikusan_SChunkNum;
int Kirikusan_BChunkNum;
int Kirikusan_BChunkSize_size;

#ifdef _TEST_ERRNO
#define Kirikusan_ERRCHECK(V,F,E) {\
		errno = 0;\
		EXPECT_EQ(F,V);\
		EXPECT_EQ(errno,E);\
		}
#else
#define Kirikusan_ERRCHECK(V,F,E) {\
                EXPECT_EQ(F,V);\
                }
#endif



void Kirikusan_chunk_measure(int* res, size_t sz)
{
	ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./file");
	*res = 0;
	int i = 0;
	while(ma_alloc(sz)!=0)
	{
		(*res)++;
		i++;
		ASSERT_EQ(1,i<Kirikusan_MEM_SIZE/Kirikusan_SChunkSize+1);
	}
	ma_deinit();
}

TEST(Kirikusan_Init, Success) {
	EXPECT_EQ(1,ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./file"));
	ma_deinit();
}

TEST(Kirikusan_LIMITSET, SET) {
	getrlimit(RLIMIT_NOFILE,&Kirikusan_maxfd);
	Kirikusan_maxfd_old = Kirikusan_maxfd;
	if((Kirikusan_maxfd.rlim_max)>Kirikusan_TIMES_INIT/2)
	{
		Kirikusan_maxfd.rlim_cur = Kirikusan_TIMES_INIT/2;
		setrlimit(RLIMIT_NOFILE,&Kirikusan_maxfd);
	}
	getrlimit(RLIMIT_NOFILE,&Kirikusan_maxfd);
	fprintf(stderr,"RLIMIT_NOFILE set: %d / %d\n",(int)Kirikusan_maxfd.rlim_cur,(int)Kirikusan_maxfd.rlim_max);
	
	Kirikusan_BChunkSize_size = sysconf(_SC_PAGESIZE);
	Kirikusan_chunk_measure(&Kirikusan_BChunkNum, Kirikusan_BChunkSize_size);
	Kirikusan_chunk_measure(&Kirikusan_SChunkNum, Kirikusan_SChunkSize);
}

TEST(Kirikusan_Init, Reinit) {
	for(int i=0;i<Kirikusan_TIMES_INIT;i++)
	{
		EXPECT_EQ(1,ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./file"));
		ma_deinit();
	}
}

TEST(Kirikusan_ECANCELED, Deinit) {
	ma_deinit();
	EXPECT_EQ(1,ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./file"));
	ma_deinit();
	ma_deinit();
}
TEST(Kirikusan_ECANCELED, Alloc) {
	Kirikusan_ERRCHECK(0,ma_alloc(100),ECANCELED);
}
TEST(Kirikusan_ECANCELED, Get) {
	Kirikusan_ERRCHECK((void*)NULL,ma_get(1),ECANCELED);
}
TEST(Kirikusan_ECANCELED, Release) {
	Kirikusan_ERRCHECK(0,ma_release(1),ECANCELED);
}
TEST(Kirikusan_ECANCELED, Free) {
	Kirikusan_ERRCHECK(0,ma_free(1),ECANCELED);
}

TEST(Kirikusan_Init, _EALREADY) {
	EXPECT_EQ(1,ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./file"));
	Kirikusan_ERRCHECK(0,ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./file"),EALREADY);
	ma_deinit();
}
TEST(Kirikusan_Init, _ENOENT) {
	errno = 0;
	EXPECT_EQ(0,ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./i o t exist/file"));
#ifdef _TEST_ERRNO
	EXPECT_NE(errno,0);
#endif
	EXPECT_EQ(1,ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./file"));
	ma_deinit();
}
TEST(Kirikusan_Init, _EINVAL) {
	Kirikusan_ERRCHECK(0,ma_init(0, Kirikusan_SWAP_SIZE, "./file"),EINVAL);
	Kirikusan_ERRCHECK(0,ma_init(0, 0, "./file"),EINVAL);
	Kirikusan_ERRCHECK(0,ma_init(Kirikusan_SWAP_SIZE, Kirikusan_MEM_SIZE, "./file"),EINVAL);
	ma_deinit();
}
TEST(Kirikusan_Init, _ENOMEM) {
	errno = 0;
//	getrlimit(RLIMIT_FSIZE,&maxfile);
//	fprintf(stderr,"Limit 2: %lld of %lld\n",(long long int)maxfile.rlim_cur,(long long int)maxfile.rlim_max);
//	maxfile.rlim_cur = Kirikusan_SWAP_SIZE00000000;
//	fprintf(stderr,"Limit 2: %lld of %lld\n",(long long int)maxfile.rlim_cur,(long long int)maxfile.rlim_max);
//	int x = setrlimit(RLIMIT_FSIZE,&maxfile);
//	perror("");
//	getrlimit(RLIMIT_FSIZE,&maxfile);
//	fprintf(stderr,"Limit 2: %lld of %lld [%d]\n",(long long int)maxfile.rlim_cur,(long long int)maxfile.rlim_max,x);
	EXPECT_EQ(0,ma_init(Kirikusan_MEM_SIZE, off_t(-5), "./file"));
//	maxfile.rlim_cur = maxfile.rlim_max;
//	setrlimit(RLIMIT_FSIZE,&maxfile);
#ifdef _TEST_ERRNO
	EXPECT_EQ(errno,ENOMEM);
#endif
	ma_deinit();
}
TEST(Kirikusan_Init, correctNumFD){
	ASSERT_EQ(1,ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./file"));
	for(int i = 0; i<Kirikusan_TIMES_INIT; i++)
	{
		ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./i o t exist/file");
		ma_init(0, Kirikusan_SWAP_SIZE, "./file");
        	ma_init(0, 0, "./file");
	        ma_init(Kirikusan_SWAP_SIZE, Kirikusan_MEM_SIZE, "./file");
		ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./file");
		ma_init(Kirikusan_MEM_SIZE, off_t(-5), "./file");
	}
	ma_deinit();
}


class Kirikusan_BasicTest : public testing::Test {
protected:  
	virtual void SetUp() {
		ASSERT_EQ(1,ma_init(Kirikusan_MEM_SIZE, Kirikusan_SWAP_SIZE, "./file"));
	}
 
	virtual void TearDown() {
		ma_deinit();
	}
};

class Kirikusan_Alloc : public Kirikusan_BasicTest {};
TEST_F(Kirikusan_Alloc, _EINVAL) {
	Kirikusan_ERRCHECK(0,ma_alloc(0),EINVAL);
}
TEST_F(Kirikusan_Alloc, _ENOMEM_1) {
	for(int i=0; i<Kirikusan_SChunkNum;i++)
		ASSERT_NE(0,ma_alloc(Kirikusan_SChunkSize));
	Kirikusan_ERRCHECK(0,ma_alloc(Kirikusan_SChunkSize),ENOMEM);
}
TEST_F(Kirikusan_Alloc, _ENOMEM_2) {
	size_t x = Kirikusan_BChunkSize_size;
	for(int i=0; i<Kirikusan_BChunkNum;i++)
		ASSERT_NE(0,ma_alloc(x));
	Kirikusan_ERRCHECK(0,ma_alloc(x),ENOMEM);
}
TEST_F(Kirikusan_Alloc, _ENOMEM_3) {
	Kirikusan_ERRCHECK(0,ma_alloc(Kirikusan_SWAP_SIZE+1),ENOMEM);
}
//TODO: Add defragmntation tests.

class Kirikusan_Get : public Kirikusan_BasicTest {};

TEST_F(Kirikusan_Get, _EFAULT_1) {
	Kirikusan_ERRCHECK((void*)NULL,ma_get(size_t(-1)),EFAULT);
}
TEST_F(Kirikusan_Get, _EFAULT_2) {
	Kirikusan_ERRCHECK((void*)NULL,ma_get(1),EFAULT);
}
TEST_F(Kirikusan_Get, _EINVAL) {
	Kirikusan_ERRCHECK((void*)NULL,ma_get(0),EINVAL);
}
TEST_F(Kirikusan_Get, Validity) {
	size_t sz = Kirikusan_BChunkSize_size;
	size_t id = ma_alloc(sz);
	void *v = ma_get(id);
	ASSERT_NE((void*)NULL,v);
	for(long long int i=0;i<Kirikusan_TIMES_GET-1;i++)
		Kirikusan_ERRCHECK(v,ma_get(id),0);
}
TEST_F(Kirikusan_Get, Repeat) {
	size_t sz = Kirikusan_BChunkSize_size;
	size_t id = ma_alloc(sz);
	for(long long int i=0;i<Kirikusan_TIMES_GET-1;i++)
	{
		void *v = ma_get(id);
		ASSERT_NE((void*)NULL,v);
		Kirikusan_ERRCHECK(v,ma_get(id),0);
		Kirikusan_ERRCHECK(1,ma_release(id),0);
		Kirikusan_ERRCHECK(1,ma_release(id),0);
	}
}
TEST_F(Kirikusan_Get, _ENOMEM) {
	size_t sz = Kirikusan_BChunkSize_size;
	for(int i=0; i<Kirikusan_MEM_SIZE/sz;i++)
		ASSERT_NE((void*)NULL,ma_get(ma_alloc(sz)));
	Kirikusan_ERRCHECK((void*)NULL,ma_get(ma_alloc(sz)),ENOMEM)
}

class Kirikusan_Release : public Kirikusan_BasicTest {};

TEST_F(Kirikusan_Release, _EFAULT_1) {
	Kirikusan_ERRCHECK(0,ma_release(size_t(-1)),EFAULT);
}
TEST_F(Kirikusan_Release, _EFAULT_2) {
	Kirikusan_ERRCHECK(0,ma_release(1),EFAULT);
}
TEST_F(Kirikusan_Release, _EINVAL) {
	Kirikusan_ERRCHECK(0,ma_release(0),EINVAL);
}
TEST_F(Kirikusan_Release, Validity) {
	size_t sz = Kirikusan_BChunkSize_size;
	size_t id = ma_alloc(sz);
	for(long long int i=0;i<Kirikusan_TIMES_GET;i++)
		ASSERT_NE((void*)NULL,ma_get(id));
	for(long long int i=0;i<Kirikusan_TIMES_GET;i++)
		Kirikusan_ERRCHECK(1,ma_release(id),0);
	Kirikusan_ERRCHECK(0,ma_release(id),EFAULT);
}

class Kirikusan_Free : public Kirikusan_BasicTest {};

TEST_F(Kirikusan_Free, _EFAULT_1) {
	Kirikusan_ERRCHECK(0,ma_free(size_t(-1)),EFAULT);
}
TEST_F(Kirikusan_Free, _EFAULT_2) {
	Kirikusan_ERRCHECK(0,ma_free(1),EFAULT);
}
TEST_F(Kirikusan_Free, _EINVAL) {
	Kirikusan_ERRCHECK(0,ma_free(0),EINVAL);
}
TEST_F(Kirikusan_Free, Passive_1) {
	size_t sz = Kirikusan_BChunkSize_size;
	size_t *t = (size_t*)malloc(sizeof(size_t)*Kirikusan_BChunkNum);
	for(long long int i=0;i<Kirikusan_BChunkNum;i++)
		ASSERT_NE(0,t[i] = ma_alloc(sz));
	for(long long int i=0;i<Kirikusan_BChunkNum;i++)
	{
		Kirikusan_ERRCHECK(1,ma_free(t[i]),0);
		Kirikusan_ERRCHECK(0,ma_free(t[i]),EFAULT);
	}
	free(t);
}
TEST_F(Kirikusan_Free, Passive_2) {
	size_t sz = Kirikusan_BChunkSize_size;
	size_t *t = (size_t*)malloc(sizeof(size_t)*Kirikusan_BChunkNum);
	for(long long int i=0;i<Kirikusan_BChunkNum;i++)
		ASSERT_NE(0,(t[i] = ma_alloc(sz)));
	for(long long int i=0;i<Kirikusan_BChunkNum;i++)
	{
		EXPECT_NE((void*)NULL, ma_get(t[i]));
		EXPECT_NE(0, ma_release(t[i]));
		Kirikusan_ERRCHECK(1,ma_free(t[i]),0);
	}
	free(t);
}
TEST_F(Kirikusan_Free, Active) {
	size_t sz = Kirikusan_BChunkSize_size;
	size_t *t = (size_t*)malloc(sizeof(size_t)*Kirikusan_BChunkNum);
	for(long long int i=0;i<Kirikusan_BChunkNum;i++)
		ASSERT_NE(0,(t[i] = ma_alloc(sz)));
	for(long long int i=0;i<Kirikusan_BChunkNum;i++)
	{
		EXPECT_NE((void*)NULL, ma_get(t[i]));
		Kirikusan_ERRCHECK(0,ma_free(t[i]),EFAULT);
		EXPECT_NE(0, ma_release(t[i]));
		Kirikusan_ERRCHECK(1,ma_free(t[i]),0);
	}
	free(t);
}

class Kirikusan_ReadWrite : public Kirikusan_BasicTest {};

TEST_F(Kirikusan_ReadWrite, PointerDereference) {
	size_t sz = Kirikusan_BChunkSize_size;
	size_t t;
	ASSERT_NE(0,(t = ma_alloc(sz)));
	char *m;
	ASSERT_NE((char *)NULL,(m = (char *)ma_get(t)));
	for(size_t i = 0; i < sz; i++)
		m[i]=42;
	for(size_t i = 0; i < sz; i++)
		ASSERT_EQ(42,m[i]);
}
TEST_F(Kirikusan_ReadWrite, Kirikusan_ReleaseBeforeRead) {
	size_t sz = Kirikusan_BChunkSize_size;
	size_t t;
	ASSERT_NE(0,(t = ma_alloc(sz)));
	char *m;
	ASSERT_NE((char *)NULL,(m = (char *)ma_get(t)));
	for(size_t i = 0; i < sz; i++)
		m[i]=42;
	ASSERT_NE(0,ma_release(t));
	ASSERT_NE((char *)NULL,m = (char *)ma_get(t));
	for(size_t i = 0; i < sz; i++)
		ASSERT_EQ(42,m[i]);
}


void* Kirikusan_Testerf(void * datav)
{
	struct Data{
		size_t *t;
		pthread_barrier_t *b;
		int l;
	} *data = (struct Data *)datav;
	int level = data->l;
	size_t sz = Kirikusan_BChunkSize_size;
	size_t *t = data->t;
	pthread_barrier_t *b = data->b;
	pthread_barrier_wait(b);
	Kirikusan_PRI();
	for(size_t i=0; i<Kirikusan_SWAP_SIZE/sz;i++)
	{
		Kirikusan_PRI();
		size_t id;
		if(!(id = ma_alloc(sz)))
			break;
		Kirikusan_PRI();
		for (size_t j = i;;j++)
			if(__sync_bool_compare_and_swap(&(t[j]),0,id))
				break;
	}
	pthread_barrier_wait(b);
	EXPECT_NE((t[Kirikusan_SWAP_SIZE/sz-1]),0);
	EXPECT_EQ((t[Kirikusan_SWAP_SIZE/sz]),0);
	pthread_barrier_wait(b);
	for(size_t i=0; i<Kirikusan_SWAP_SIZE/sz-1;i++)
	{
		EXPECT_NE((ma_get(t[i])),(void*)NULL);
	}
	pthread_barrier_wait(b);
	if(level==1)
	{
		long long int* v = (long long int*)ma_get(t[0]);
		*v = 0;
		pthread_barrier_wait(b);
		for(size_t i=0;i<Kirikusan_TIMES_GET*100;i++)
		{
			__sync_fetch_and_add(v,1);
		}
		pthread_barrier_wait(b);
		EXPECT_EQ(*v,Kirikusan_TIMES_GET*100*(Kirikusan_SWAP_SIZE/sz));
		pthread_barrier_wait(b);
	}
	if(level==2)
	{
		long long int** v = (long long int**)malloc(sizeof(long long int*)*(Kirikusan_SWAP_SIZE/sz));
		for(size_t i=0;i<Kirikusan_SWAP_SIZE/sz;i++)
		{
			v[i] = (long long int*)ma_get(t[i]);
			*(v[i]) = 0;
		}
		pthread_barrier_wait(b);
		for(size_t i=0;i<Kirikusan_TIMES_GET;i++)
		{
			int j = rand()%(Kirikusan_SWAP_SIZE/sz);
			EXPECT_EQ((char*)v[j],ma_get(t[j]));
			__sync_fetch_and_add(v[rand()%(Kirikusan_SWAP_SIZE/sz)],1);
			EXPECT_EQ(1,ma_release(t[j]));
		}
		pthread_barrier_wait(b);
		long long val = 0;
		for(size_t i=0;i<Kirikusan_SWAP_SIZE/sz;i++)
			val+=*(v[i]);
		EXPECT_EQ(val,Kirikusan_TIMES_GET*(Kirikusan_SWAP_SIZE/sz));
		pthread_barrier_wait(b);
	}
	for(size_t i=0; i<Kirikusan_SWAP_SIZE/sz;i++)
	{
		Kirikusan_PRI();
		size_t id = t[i];
		if(!__sync_bool_compare_and_swap(&(t[i]),id,0))
			continue;
		Kirikusan_PRI();
		ma_free(id);
	}
	for(size_t i=0; i<Kirikusan_SWAP_SIZE/sz-1;i++)
	{
		EXPECT_EQ((ma_get(t[i])),(void*)NULL);
	}
	pthread_exit(NULL);
}
#define Kirikusan_Serious_Tester(LEVEL) \
	size_t sz = Kirikusan_BChunkSize_size;\
	size_t *t = (size_t*)calloc(Kirikusan_SWAP_SIZE/sz+10,sizeof(size_t));\
	pthread_t *p = (pthread_t*)malloc(sizeof(pthread_t)*(Kirikusan_SWAP_SIZE/sz));\
	pthread_barrier_t *b = (pthread_barrier_t*)malloc(sizeof(pthread_barrier_t));\
	pthread_barrier_init (b,NULL,Kirikusan_SWAP_SIZE/sz);\
	struct Data{\
		size_t *t;\
		pthread_barrier_t *b;\
		int l;\
	} data;\
	data.t = t;\
	data.b = b;\
	data.l = LEVEL;\
\
	ma_init (Kirikusan_SWAP_SIZE, Kirikusan_SWAP_SIZE, "./file");\
	for(size_t i = 0; i < Kirikusan_SWAP_SIZE/sz; i++)\
		pthread_create(&(p[i]),NULL,Kirikusan_Testerf,&data);\
	Kirikusan_PRI();\
\
	for(size_t i = 0; i < Kirikusan_SWAP_SIZE/sz; i++)\
		pthread_join(p[i],NULL);\
	ma_deinit();

TEST(Kirikusan_Serious, Tester0) {
	Kirikusan_Serious_Tester(0);
}
TEST(Kirikusan_Serious, Tester1) {
	Kirikusan_Serious_Tester(1);
}
TEST(Kirikusan_Serious, Tester2) {
	Kirikusan_Serious_Tester(2);
}

TEST(Kirikusan_LIMITCLEAN, CLEAN){
        setrlimit(RLIMIT_NOFILE,&Kirikusan_maxfd_old);
	fprintf(stderr,"RLIMIT_NOFILE set: %d / %d\n",(int)Kirikusan_maxfd_old.rlim_cur,(int)Kirikusan_maxfd_old.rlim_max);
}
